
#include "heightmap.h"
#include <limits>

using namespace WtfEngine;
using namespace WtfGraphics;

/**
 * Convert a 32 bit per channel floating point pixel to a height. Colour values are expected to
 * range from 0 - 1.
 *	Height = Red + (Green * 256) + (Blue * 256 * 256)
 **/
inline tScalar __FloatPixelToHeight(const PixelType::tRGBA32f::tPixelType& px) {
	return (px[0] + (px[1] * 256) + (px[2] * 256 * 256));
};
inline PixelType::tRGBA32f::tPixelType __HeightToFloatPixel(tScalar h) {
	PixelType::tRGBA32f::tPixelType px;
	px[2] = h / (256.0 * 256.0);
	h -= (px[2] * 256.0 * 256.0);
	px[1] = (h / 256.0);
	h -= (px[1] * 256.0);
	px[0] = h;

	return px;
};

/**
 * Convert an 8 bit per channel pixel to a height. Colour values are expected to
 * range from 0 - 255.
 *	Height = (Red / 256) + Green + (Blue * 256)
 **/
inline tScalar __BytePixelToHeight(const PixelType::tRGBA8::tPixelType& px) {
	return (((tScalar)px[0]) / 256.0) + (tScalar)px[1] + (((tScalar)px[2]) * 256.0);
};
inline PixelType::tRGBA8::tPixelType __HeightToBytePixel(tScalar h) {
	PixelType::tRGBA8::tPixelType px;
	px[2] = (unsigned char)(h / 256.0);
	h -= (((tScalar)px[2]) * 256.0);
	px[1] = (unsigned char) h;
	h -= px[1];
	px[0] = (unsigned char)(h * 256.0);

	return px;
};


Heightmap::~Heightmap() {
	Destroy();
};

void Heightmap::ReadImage(const WtfEngine::Image::tRef& rImg) {
	WtfEngine::Image::tPixelBuffer::tRef rPixels(rImg->getPixels());

	Destroy();
	mvDim = rImg->getSize();

	mvfHeight = new tScalar[mvDim[0] * mvDim[1]];
	for(int i = 0; i < mvDim[0] * mvDim[1]; i++) {
		mvfHeight[i] = __BytePixelToHeight((*rPixels)[i]);
	};
};

/// Calcuates the required scale factor to produce a terrain of the specified
/// maximum size.
tVector Heightmap::CalculateScale(const tVector& vDim) const {
	tScalar fMaxH = -std::numeric_limits<tScalar>::infinity();
	tScalar fMinH = std::numeric_limits<tScalar>::infinity();
	for(int i = 0; i < mvDim[0] * mvDim[1]; i++) {
		fMaxH = maxf(mvfHeight[i], fMaxH);
		fMinH = minf(mvfHeight[i], fMinH);
	};

	return tVector(vDim[0] / (tScalar)mvDim[0], vDim[1] / (fMaxH - fMinH), vDim[2] / (tScalar)mvDim[1]);
};

PhysicalModels::StaticHeightmapModel::tCacheRef Heightmap::getStaticModel() const
{
	String sName = (*getName()) + "$staticmodel";
	DataManager::GetInstance().RegisterReconstructor(sName,	(
			new StaticFunctor<WtfEngine::IDataObject*, const StringPool::Entry&, const StringPool::Entry&>(
				(void*)&PhysicalModels::StaticHeightmapModel::CreateFromHm)
		)->Bind(getName()));
	return PhysicalModels::StaticHeightmapModel::tCacheRef(sName);
};

void Heightmap::Destroy() 
{
	if(mvfHeight) {
		delete [] mvfHeight;
		mvfHeight = NULL;
	};
};

WtfEngine::IDataObject * Heightmap::Load(const WtfEngine::StringPool::Entry &sName) {
	Heightmap * pHm = new Heightmap(sName);
	pHm->ReadImage(Image::tCacheRef(sName));
	return pHm;
};


/**** Physical Model ****/

inline btCollisionShape * __CalcStaticHeightmapModel(const Heightmap::tRef& rHm, unsigned uLod = 12)
{
	/*btTriangleMesh  * pShape = new btTriangleMesh();

	// This is the tacky way of doing things, duplicating meshes..
	for(int i = 0; i < rMesh->getNumTris(); i++) {
		pShape->addTriangle(FROMVECT(rMesh->getTriangles()[i][0]),
			FROMVECT(rMesh->getTriangles()[i][1]), FROMVECT(rMesh->getTriangles()[i][2]));
	};

	return new btBvhTriangleMeshShape(pShape, true);
*/
	return NULL;
};

PhysicalModels::StaticHeightmapModel::StaticHeightmapModel(const WtfEngine::StringPool::Entry& sHmName, bool bRegisterReconstructor)
: PhysicalModel(*sHmName + "$staticmodel", __CalcStaticHeightmapModel(Heightmap::tCacheRef(sHmName)), 0)
{
	if(bRegisterReconstructor) {
		DataManager::GetInstance().RegisterReconstructor(getName(),	(
				new StaticFunctor<WtfEngine::IDataObject*, const StringPool::Entry&, const StringPool::Entry&>(
					&PhysicalModels::StaticHeightmapModel::CreateFromHm)
			)->Bind(sHmName));
	};
};

WtfEngine::IDataObject * PhysicalModels::StaticHeightmapModel::CreateFromHm(const WtfEngine::StringPool::Entry& sName,
																   const WtfEngine::StringPool::Entry& sHmName)
{
	return new PhysicalModels::StaticHeightmapModel(sHmName);
};
